研报复制(三):基于相对强弱指标的大小盘轮动
作者:量化小白H
个人公众号:量化小白上分记
本文是对报告《20100210-华泰证券-数量化策略:大小盘轮动》、《20161218-中金公司-价量视角下的市值风格轮动策略》部分内容的复制,文章为个人理解,不保证正确性,请理性看待,欢迎指正。
A股市场上存在着明显的大小盘轮动的现象,一段时间大盘表现强势,一段时间小盘表现强势,所谓二八轮动。这种现象提供了构建大小盘轮动策略的可能,目前常见的两种构建大小盘轮动策略的方式分别为
宏观层面出发,分析影响市场价格变动的政策、利率、GDP、供需数据等,以此构建模型分析市场当前的大小盘风格;
技术角度出发,通过量价数据构建指标,分析市场当前的大小盘风格。
两种方式各有优劣,本文复制的两篇报告均属于第二种方式。
以交易时点划分,可以将市场上的交易行为划分为两类:左侧交易与右侧交易。
左侧交易:在价格即将达到某个支撑点时逆向进入市场,做反转。
右侧交易:在价格走出趋势之后进入市场,做动量,也就是常说的追涨杀跌。
本文选取的两种策略分别属于这两种策略,比较说明在同一指标下,这两种交易行为的优劣,但不具有普遍性。
大盘指数:HS300
小盘指数:ZZ500
回测区间:2015年1月1日 - 2018年12月2日
数据来源:WIND
注:后台回复“代码”可获取代码文件和研报
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r'c:\windows\fonts\simsun.ttc',size = 20)
from WindPy import *
w.start()
dateStart = date(2005,1,1)
dateEnd = date(2018,12,2)
IndexClose = w.wsd("000300.SH,000905.SH", "close", "{}".format(dateStart), "{}".format(dateEnd), "")
Indexprice = pd.DataFrame(np.array(IndexClose.Data).T,columns=['hs300','ZZ500'],index = IndexClose.Times)
通过大小盘指数收盘价格构造相对强弱指数:
其中P1是中证500点位,P2是沪深300点位,回测区间内的相对强弱指标如图所示
代码(全文作图代码基本相同,之后不再列出)
Indexprice['P'] = np.log(Indexprice.ZZ500) - np.log(Indexprice.hs300)
X = np.arange(Indexprice.shape[0])
xticklabel = Indexprice.index
xticks = np.arange(0,Indexprice.shape[0]+1,np.int((Indexprice.shape[0]+1)/5))
plt.figure(figsize = [20,4])
SP = plt.axes()
P1 = SP.plot(X,Indexprice.hs300,linewidth = 2,label = 'HS300',color = 'darkred')
P2 = SP.plot(X,Indexprice.ZZ500,linewidth = 2,label = 'ZZ500',color = 'cornflowerblue')
SP1 = SP.twinx()
P3 = SP1.plot(X,Indexprice['P'],color = 'orange',linewidth = 2,label = u'相对强弱指数')
SP.set_xticks(xticks)
SP.set_xticklabels(xticklabel[xticks],size = 20)
p = P1+P2+P3
lns = [l.get_label() for l in p]
plt.legend(p,lns,prop=font)
plt.show()
相对强弱指标的值大小没有意义,并不是说大于0就倾向于小盘,小于0就倾向于大盘,两个指数的点数之间数量上有差异,因此要关注指标的变化趋势。
指标呈上升趋势,说明当前市场为小盘风格,应该投资小盘指数,反之应投资大盘指数
策略1为左侧交易,首先计算指标的MA10对数据进行平滑,通过函数updownbound计算MA10的布林带,若MA10上穿下轨,做多小盘股,若MA10下穿上轨,做多大盘。
布林带定义
def updownbound(data,n,c):
data['upbound'] = data['P'].rolling(window = n, center = False).mean() + c * data['P'].rolling(window = n, center = False).std()
data['upbound'] = data['upbound'].shift(1)
data['downbound'] = data['P'].rolling(window = n, center = False).mean() - c * data['P'].rolling(window = n, center = False).std()
data['downbound'] = data['downbound'].shift(1)
for i in range(2,n):
data.loc[i,'upbound'] = data.loc[0:i - 1,'P'].mean() + c * data.loc[0:i - 1,'P'].std()
data.loc[i,'downbound'] = data.loc[0:i - 1,'P'].mean() - c * data.loc[0:i - 1,'P'].std()
data.loc[0,'upbound'] = 0
data.loc[0,'downbound'] = 0
data.loc[1,'upbound'] = data.loc[0,'P']
data.loc[1,'downbound'] = data.loc[0,'P']
return(data)
相对强弱指数、MA10及布林带如下
策略规则
当 10 日均线从下方上穿下轨,则做多小盘股,卖出大盘股,第二天开始算收益率。
当 10 日均线从上方下穿上轨,则做空小盘股,做多大盘股,第二天开始算收益率。
其他的情形,不需要做什么操作,任何时候都是满仓大盘股或者小盘股。
def strategy(data):
data['flag_hs'] = 0
data['flag_zz'] = 0
data['ret_strategy'] = 0
data['ret_zz'] = data['ZZ500'].pct_change(1)
data['ret_hs'] = data['hs300'].pct_change(1)
data.loc[0,'ret_zz'] = 0
data.loc[0,'ret_hs'] = 0
for i in range(1,data.shape[0] - 1):
# 上穿下轨,做多小盘,卖出大盘
if data.ma10[i - 1] < data.downbound[i - 1] and data.ma10[i] > data.downbound[i]:
data.loc[i + 1 ,'flag_zz'] = 1
data.loc[i + 1 ,'flag_hs'] = 0
# data.loc[i + 1,'net_zz'] = data.loc[i ,'net_zz']*(1 + data.loc[i + 1,'ret_zz'])
# data.loc[i + 1,'net_hs'] = data.loc[i ,'net_hs']*(1 - data.loc[i + 1,'ret_hs'])
# 下穿上轨,做多大盘,卖出小盘
elif data.ma10[i - 1] > data.upbound[i - 1] and data.ma10[i] < data.upbound[i]:
data.loc[i + 1 ,'flag_zz'] = 0
data.loc[i + 1 ,'flag_hs'] = 1
# data.loc[i + 1,'net_zz'] = data.loc[i ,'net_zz']*(1 - data.loc[i + 1,'ret_zz'])
# data.loc[i + 1,'net_hs'] = data.loc[i ,'net_hs']*(1 + data.loc[i + 1,'ret_hs'])
else:
data.loc[i + 1 ,'flag_zz'] = data.loc[i ,'flag_zz']
data.loc[i + 1 ,'flag_hs'] = data.loc[i ,'flag_hs']
data.loc[data['flag_zz'] ==1,'ret_strategy'] = data.loc[data['flag_zz'] ==1,'ret_zz']
data.loc[data['flag_hs'] ==1,'ret_strategy'] = data.loc[data['flag_hs'] ==1,'ret_hs']
data['net_strategy_zz'] = (1 + data['flag_zz']*data['ret_zz']).cumprod()
data['net_strategy_hs'] = (1 + data['flag_hs']*data['ret_hs']).cumprod()
data['net_strategy'] = (1 + data['ret_strategy']).cumprod()
return data
回测结果如下,蓝色为策略收益,浅蓝色为小盘指数的净值,红色为大盘指数的净值,绿色为持仓情况,值为1表示持有的是大盘,值为2表示持有的是小盘。
策略净值为4.96,整体来看,这种策略的优势仅在于交易次数很小,但基本没有超额收益。
再从其他方面考察策略:
上图为策略相对于大盘和小盘指数的表现,可以看出策略在16年之前优于大盘,而在整个区间内与小盘指数基本持平。
分年统计来看,策略并没有表现出持续优于大盘指数或者优于小盘指数。
整体来看,策略效果不尽人意。分析原因,主要在于市场在2008-2015年之间出现了长期的震荡行情,16年之后至今也是震荡下行,没有出现明显的趋势性,因此很难触发策略条件。
策略2采取趋势突破的方法,创新高时买入小盘指数,创新低时买入大盘指数。
报告中相对强弱指数定义如下
与前文定义的指标相差一个常数,对结果没有影响,仍采用前文定义。
策略规则:
相对强弱指数创新N1日新高,持有小盘
相对强弱指数创N2日新低,持有大盘
def strategy1(data,N1,N2,fee_ratio = 0):
data['flag_hs'] = 0
data['flag_zz'] = 0
data['ret_strategy'] = 0
data['buysell'] = 0
data['ret_zz'] = data['ZZ500'].pct_change(1).fillna(0)
data['ret_hs'] = data['hs300'].pct_change(1).fillna(0)
for i in range(max(N1,N2),data.shape[0] - 1):
# 创新高,做多小盘
if data.P[i] >= np.max(data.P[i - N1:i]):
data.loc[i + 1 ,'flag_zz'] = 1
data.loc[i + 1 ,'flag_hs'] = 0
# data.loc[i + 1,'net_zz'] = data.loc[i ,'net_zz']*(1 + data.loc[i + 1,'ret_zz'])
# data.loc[i + 1,'net_hs'] = data.loc[i ,'net_hs']*(1 - data.loc[i + 1,'ret_hs'])
# 创新低,做多大盘
elif data.P[i] <= np.min(data.P[i - N2:i]):
data.loc[i + 1 ,'flag_zz'] = 0
data.loc[i + 1 ,'flag_hs'] = 1
# data.loc[i + 1,'net_zz'] = data.loc[i ,'net_zz']*(1 - data.loc[i + 1,'ret_zz'])
# data.loc[i + 1,'net_hs'] = data.loc[i ,'net_hs']*(1 + data.loc[i + 1,'ret_hs'])
else:
data.loc[i + 1 ,'flag_zz'] = data.loc[i ,'flag_zz']
data.loc[i + 1 ,'flag_hs'] = data.loc[i ,'flag_hs']
data.loc[data['flag_zz'] ==1,'ret_strategy'] = data.loc[data['flag_zz'] ==1,'ret_zz']
data.loc[data['flag_hs'] ==1,'ret_strategy'] = data.loc[data['flag_hs'] ==1,'ret_hs']
data.loc[data['flag_hs'] != data.flag_hs.shift(1),'buysell'] = 1
data.loc[0,'buysell'] = 0
data['ret_strategy'] = data['ret_strategy'] - fee_ratio*data['buysell']
#data['net_strategy_zz'] = (1 + data['flag_zz']*data['ret_zz']).cumprod()
#data['net_strategy_hs'] = (1 + data['flag_hs']*data['ret_hs']).cumprod()
data['net_strategy'] = (1 + data['ret_strategy']).cumprod()
return data
fee_ratio为手续费率,设定为0,取N1 = 20,N2 = 20,回测结果如下
最优参数下,策略净值为7.52,从净值曲线来看,明显优于策略1,有超额回报,但交易次数增多,交易成本也会上升。
此外,今年以来策略出现明显回撤,但整个市场都在下跌,可以通过策略与大小盘指数的相对强弱来说明策略相对于市场的表现
可以看出,策略2持续性优于或持平大小盘指数。
分年统计表明,除了16,17年,策略相对于沪深300每年都有正收益。
总体来看,策略2明显优于策略1(但并不是说右侧交易一定优于左侧交易,只是这一指标下)。
中金报告中指数,N1,N2处于15-25时,策略表现都很好。对N1,N2从5到30进行遍历,看看策略在不同参数下的表现情况。
横纵轴分别为N1,N2,竖轴/颜色表示策略累计收益率,颜色越深表明策略累积收益率越大,从图可以看出,最优参数存在于平面左下区域,即N1,N2较小时。
取最优参数N1 = 8,N2 = 10做回测结果如下
最优参数下,净值为12.78。
相对于市场走势平稳,回测区间内均持平或上升。
分年统计来看,除个别年份,每年对于大小盘指数都有明显超额收益,表现很好。
1. 20100210-华泰证券-数量化策略:大小盘轮动
2. 20161218-中金公司-量化策略专题风格轮动研究(1):价量视角下的市值风格轮动策略
Python的爱好者社区历史文章大合集:
关注后在公众号内回复“ 课程 ”即可获取:
小编的转行入职数据科学(数据分析挖掘/机器学习方向)【最新免费】
小编的Python的入门免费视频课程!
小编的Python的快速上手matplotlib可视化库!
崔老师爬虫实战案例免费学习视频。
陈老师数据分析报告扩展制作免费学习视频。
玩转大数据分析!Spark2.X + Python精华实战课程免费学习视频。